//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//********************************************************* 

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using Microsoft.TridentWordAddIn.Common;
using Microsoft.TridentWordAddIn.ResourceHelper;
using Microsoft.TridentWordAddIn.Server;

namespace Microsoft.TridentWordAddIn.Presentation
{
    /// <summary>
    /// The controller of the Trident Package
    /// </summary>
    public class Controller : UIController, IController
    {
        #region Fields

        private readonly ModelDictionary modelCollection;
        private static Controller instance;
        private UserData userData;
        private IDataStore documentStore;
        private IThisAddInPackageHost packageHost;
        private ITridentServer tridentServer;
        private Dictionary<int, RibbonControlStateManager> ribbonControlsState;

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="Controller"/> class.
        /// </summary>
        /// <param name="userControlCreator">The user control creator.</param>
        private Controller(IUserControlCreator userControlCreator)
            : base(userControlCreator)
        {
            this.modelCollection = new ModelDictionary();
            this.ribbonControlsState = new Dictionary<int, RibbonControlStateManager>();
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Occurs when [on connection changed].
        /// </summary>
        public event EventHandler<ConnectionChangedEventArgs> OnConnectionChanged;

        /// <summary>
        /// Occurs when [on picture selection changed].
        /// </summary>
        public event EventHandler<PictureSelectionChangedEventArgs> OnPictureSelectionChanged;

        /// <summary>
        /// Occurs when [on association changed].
        /// </summary>
        public event EventHandler<AssociationChangedEventArgs> OnAssociationChanged;

        /// <summary>
        /// Occurs when [on rerun completed].
        /// </summary>
        public event EventHandler<RerunCompletedEventArgs> OnRerunCompleted;

        /// <summary>
        /// Occurs when [on association progress].
        /// </summary>
        public event EventHandler<ProgressEventArgs> AssociationProgress;

        /// <summary>
        /// Occurs when [on rerun clicked].
        /// </summary>
        public event EventHandler<SelectedAssociationEventArgs> OnRerunClicked;

        /// <summary>
        /// Occurs when [on document closing].
        /// </summary>
        public event EventHandler<BaseEventArgs> OnDocumentClosing;

        #endregion

        #region IContoller Properties

        /// <summary>
        /// Gets the trident server.
        /// </summary>
        /// <value>The trident server.</value>
        public ITridentServer Server
        {
            get
            {
                this.tridentServer = this.tridentServer ?? this.GetTridentServer();
                return this.tridentServer;
            }
        }

        /// <summary>
        /// Gets or sets the data store.
        /// </summary>
        /// <value>The data store.</value>
        public IDataStore DataStore
        {
            get { return this.documentStore; }
            set { this.documentStore = value; }
        }

        /// <summary>
        /// Gets the document count.
        /// </summary>
        /// <value>The document count.</value>
        public int DocumentsCount
        {
            get { return this.packageHost.DocumentsCount; }
        }

        /// <summary>
        /// Gets the document key.
        /// </summary>
        /// <value>The document key.</value>
        public int CurrentDocumentKey
        {
            get { return this.packageHost.ActiveDocumentKey; }
        }

        /// <summary>
        /// Gets or sets the active connection for document.
        /// </summary>
        /// <value>The active connection for document.</value>
        public string CurrentConnectionName
        {
            get
            {
                int documentKey = this.CurrentDocumentKey;
                if (this.ribbonControlsState.ContainsKey(documentKey))
                {
                    return this.ribbonControlsState[documentKey].Connection;
                }

                return string.Empty;
            }

            set
            {
                int documentKey = this.CurrentDocumentKey;
                if (this.ribbonControlsState.ContainsKey(documentKey))
                {
                    this.ribbonControlsState[documentKey].Connection = value;
                }
                else
                {
                    RibbonControlStateManager controlState = new RibbonControlStateManager();
                    controlState.Connection = value;
                    this.ribbonControlsState.Add(documentKey, controlState);
                }
            }
        }

        /// <summary>
        /// Gets the user data.
        /// </summary>
        /// <value>The user data.</value>
        public UserData UserData
        {
            get
            {
                this.userData = this.userData ?? UserDataManager.GetData();
                return this.userData;
            }
        }

        /// <summary>
        /// Gets or sets the current connection.
        /// </summary>
        /// <value>The current connection.</value>
        public TridentConnection CurrentConnection
        {
            get
            {
                string connectionName = this.CurrentConnectionName;
                if (connectionName.IsNullOrEmpty())
                {
                    connectionName = UserData.CurrentConnectionName;
                }

                if (!connectionName.IsNullOrEmpty() && UserData.Connections.ContainsKey(connectionName))
                {
                    return UserData.Connections[connectionName];
                }

                return null;
            }

            set
            {
                if (value == null)
                {
                    UserData.CurrentConnectionName = null;
                }
                else
                {
                    UserData.CurrentConnectionName = value.Name;
                    this.CurrentConnectionName = value.Name;

                    if (UserData.Connections.ContainsKey(UserData.CurrentConnectionName))
                    {
                        UserData.Connections[UserData.CurrentConnectionName] = value;
                    }
                    else
                    {
                        UserData.Connections.Add(UserData.CurrentConnectionName, value);
                    }
                }

                this.Server.CurrentTridentConnection = value;
            }
        }

        /// <summary>
        /// Gets or sets the selected picture.
        /// </summary>
        /// <value>The selected picture.</value>
        public DocumentPicture SelectedPicture
        {
            get
            {
                return this.packageHost.GetSelectedPicture();
            }
            set
            {
                this.packageHost.SelectPicture(this.CurrentDocumentKey, value.Key);
            }
        }

        /// <summary>
        /// Gets the range.
        /// </summary>
        /// <value>The range.</value>
        public PictureRange Range
        {
            get
            {
                return this.packageHost.DocumentRange;
            }
        }

        /// <summary>
        /// Gets the server broker.
        /// </summary>
        /// <value>The server broker.</value>
        public IServerBroker ServerBroker
        {
            get { return new ServerBroker(this.userData); }
        }

        #endregion

        /// <summary>
        /// Instances the specified user control creator.
        /// </summary>
        /// <param name="userControlCreator">The user control creator.</param>
        /// <returns>Controller</returns>
        public static Controller Instance(IUserControlCreator userControlCreator)
        {
            instance = instance ?? new Controller(userControlCreator);
            return instance;
        }

        #region IController Members

        /// <summary>
        /// Initializes the base ribbon invalidator.
        /// </summary>
        /// <param name="ribbonInvalidator">The ribbon invalidator.</param>
        public override void InitializeBaseRibbonInvalidator(IRibbonInvalidator ribbonInvalidator)
        {
            base.RibbonInvalidator = ribbonInvalidator;
        }

        /// <summary>
        /// Sets the package host.
        /// </summary>
        /// <param name="taskPaneManager">The task pane manager.</param>
        public override void InitializeBaseTaskPaneManager(ITaskPaneManager taskPaneManager)
        {
            base.TaskPaneManager = taskPaneManager;
        }

        /// <summary>
        /// Sets the package host.
        /// </summary>
        /// <param name="packageHost">The package host.</param>
        public void SetPackageHost(IThisAddInPackageHost packageHost)
        {
            this.packageHost = packageHost;
            this.packageHost.DocumentSave += new EventHandler<DocumentSaveEventArgs>(this.packageHost_DocumentSaving);
            this.packageHost.DocumentClose += new EventHandler<CancelCloseEventArgs>(this.packageHost_DocumentClosing);
            this.packageHost.DocumentOpen += new EventHandler(this.packageHost_DocumentOpen);
            this.packageHost.DocumentActivated += new EventHandler(this.packageHost_DocumentActivated);
            this.packageHost.OnPictureSelectionChanged += new EventHandler<PictureSelectionChangedEventArgs>(packageHost_OnPictureSelectionChanged);
            this.packageHost.OnAddInShutdown += new EventHandler<EventArgs>(packageHost_OnAddInShutdown);
        }

        /// <summary>
        /// Gets the current model.
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        /// <returns>Model</returns>
        public Model GetModelByDocumentKey(int documentKey)
        {
            Model model = null;
            if (this.modelCollection.ContainsKey(documentKey))
            {
                model = this.modelCollection[documentKey];
            }
            else
            {
                model = this.DataStore.DeserializeModel(documentKey);
                model = model ?? new Model();
                this.modelCollection.Add(documentKey, model);
                this.UpdateRibbon();
            }

            return model;
        }

        /// <summary>
        /// Gets the connectio name by document key.
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        /// <returns>string</returns>
        public string GetConnectionByDocumentKey(int documentKey)
        {
            if (this.ribbonControlsState.ContainsKey(documentKey))
            {
                return this.ribbonControlsState[documentKey].Connection;
            }

            return string.Empty;
        }

        /// <summary>
        /// Sets the connection name by document key.
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        /// <param name="connectionName">Name of the connection.</param>
        public void SetConnectionByDocumentKey(int documentKey, string connectionName)
        {
            if (this.ribbonControlsState.ContainsKey(documentKey))
            {
                this.ribbonControlsState[documentKey].Connection = connectionName;
            }
            else
            {
                RibbonControlStateManager ribbonStateMgr = new RibbonControlStateManager();
                ribbonStateMgr.Connection = connectionName;
                this.ribbonControlsState.Add(documentKey, ribbonStateMgr);
            }
        }

        /// <summary>
        /// Shows the server connection form.
        /// </summary>
        /// <param name="connection">The connection.</param>
        /// <param name="isTempConnection">if set to <c>true</c> [is temp connection].</param>
        /// <returns>bool</returns>
        public bool ShowServerConnectionForm(ref TridentConnection connection, bool isTempConnection)
        {
            bool success = false;
            using (ConnectionForm serverConnection = new ConnectionForm(ref connection, true, false, isTempConnection))
            {
                if (DialogResult.OK == serverConnection.ShowDialog())
                {
                    if (serverConnection.IsConnectionSuccessfull)
                    {
                        success = true;
                        if (connection != null)
                        {
                            connection = serverConnection.Connection;
                        }
                    }
                    else
                    {
                        MessageBoxHelper.ShowDefault("Connection Failed", MessageBoxIcon.Error);
                    }
                }
            }

            return success;
        }

        /// <summary>
        /// Shows the Trident server connection form.
        /// </summary>
        /// <param name="connection">The connection.</param>
        /// <param name="editMode">if set to <c>true</c> [edit mode].</param>
        /// <param name="deleteOnCancel">if set to <c>true</c> [delete on cancel].</param>
        /// <returns>bool</returns>
        public bool ShowServerConnectionForm(ref TridentConnection connection, bool editMode, bool deleteOnCancel)
        {
            bool success = false;
            using (ConnectionForm serverConnection = new ConnectionForm(ref connection, editMode, deleteOnCancel, false))
            {
                if (DialogResult.OK == serverConnection.ShowDialog())
                {
                    if (serverConnection.IsConnectionSuccessfull)
                    {
                        success = true;
                        if (connection != null)
                        {
                            connection = serverConnection.Connection;
                        }
                    }
                    else
                    {
                        MessageBoxHelper.ShowDefault("Connection Failed");
                    }
                }
            }

            return success;
        }

        /// <summary>
        /// Removes the connection.
        /// </summary>
        /// <param name="connectionName">Name of the connection.</param>
        /// <returns>bool</returns>
        public bool RemoveConnection(string connectionName)
        {
            bool isRemoved = false;
            Dictionary<string, TridentConnection>.KeyCollection keyCollection = this.UserData.Connections.Keys;
            foreach (string key in keyCollection)
            {
                if (key.CompareIgnoreCase(connectionName))
                {
                    isRemoved = this.userData.Connections.Remove(key);
                    break;
                }
            }
            
            if (isRemoved)
            {
                this.UpdateActiveConnectionList(connectionName);
            }

            return isRemoved;
        }

        /// <summary>
        /// Documents the protection.
        /// </summary>
        public void DocumentProtection()
        {
            if (this.packageHost.IsDocumentProtected(this.CurrentDocumentKey))
            {
                using (UnprotectDocumentForm unprotectDocumentForm = new UnprotectDocumentForm(this.packageHost))
                {
                    unprotectDocumentForm.ShowDialog();
                }
            }
            else
            {
                using (ProtectDocumentForm protectDocumentForm = new ProtectDocumentForm())
                {
                    protectDocumentForm.ShowDialog();
                    if (DialogResult.OK == protectDocumentForm.DialogResult)
                    {
                        this.packageHost.ProtectDocument(protectDocumentForm.Password);
                    }
                }
            }
        }

        /// <summary>
        /// Shows the task pane.
        /// </summary>
        /// <param name="taskPaneKey">The task pane key.</param>
        /// <param name="isPressed">if set to <c>true</c> [is pressed].</param>
        public void ShowTaskPane(TaskPaneKey taskPaneKey, bool isPressed)
        {
            base.ShowCustomTaskPane(taskPaneKey, isPressed, this.CurrentDocumentKey);
        }

        /// <summary>
        /// Determines whether [is task pane visible] [the specified control id].
        /// </summary>
        /// <param name="controlId">The control id.</param>
        /// <returns>
        /// 	<c>true</c> if [is task pane visible] [the specified control id]; otherwise, <c>false</c>.
        /// </returns>
        public bool IsTaskPaneVisible(string controlId)
        {
            return base.IsTaskPaneVisible(controlId, this.CurrentDocumentKey);
        }

        /// <summary>
        /// Invalidates the control.
        /// </summary>
        /// <param name="controlKey">The control key.</param>
        public new void InvalidateControl(ControlKey controlKey)
        {
            base.InvalidateControl(controlKey);
        }

        /// <summary>
        /// Reruns the workflow.
        /// </summary>
        /// <param name="server">The server.</param>
        /// <param name="rerunWorkflowParam">The rerun workflow param.</param>
        /// <returns>Guid</returns>
        public Guid SumbitJob(ITridentServer server, RerunWorkflowInformation rerunWorkflowParam)
        {
            if (!server.IsWorkflowExists(rerunWorkflowParam.Workflow.Id))
            {
                //ToDo Install/Deploy workflow at Server
            }

            return server.RunWorkflow(rerunWorkflowParam);
        }

        /// <summary>
        /// Associates the workflow.
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        /// <param name="workflow">The workflow.</param>
        /// <param name="association">The association.</param>
        /// <param name="server">The server.</param>
        public void AssociateWorkflow(int documentKey, Workflow workflow, WorkflowAssociation association, ITridentServer server)
        {
            this.Progress(documentKey, "Workflow association Started...");

            Model currentModel = this.GetModelByDocumentKey(documentKey);
            Image image = null;
            if (association.Picture != null)
            {
                image = this.packageHost.GetSelectedPicture(documentKey, association.Picture.Key);
            }

            if (currentModel.Workflows.ContainsKey(workflow.Id))
            {
                Workflow alreadyStoredWorkflow = currentModel.Workflows[workflow.Id];
                if (alreadyStoredWorkflow.Linked && !workflow.Linked)
                {
                    this.StoreWorkflow(documentKey, workflow.Id, server);
                }

                alreadyStoredWorkflow.Linked = workflow.Linked;
                workflow = alreadyStoredWorkflow;
            }
            else
            {
                workflow.LinkedConnection = this.CurrentConnection.Clone() as TridentConnection;
                TridentConnection.ClearCredentials(workflow.LinkedConnection);
                if (!workflow.Linked)
                {
                    this.StoreWorkflow(documentKey, workflow.Id, server);
                }
            }

            this.DownloadAndStoreJobOutput(documentKey, association.Job.Id, server);
            association.ThumbnailImage = image.GetThumbnailImage();
            association.WorkflowId = workflow.Id;
            currentModel.AddWorkflow(workflow.Clone() as Workflow);
            currentModel.AddAssociation(association);

            this.Progress(documentKey, "Workflow association completed!");
        }

        /// <summary>
        /// Inserts the trident link.
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        /// <param name="range">The range.</param>
        /// <param name="association">The association.</param>
        public void InsertTridentLink(int documentKey, PictureRange range, WorkflowAssociation association)
        {
            Image image = ResourceFetcher.GetImage("TridentLink").FormatLinkImage(association.Name);
            PictureRange insertedRange = this.packageHost.InsertPicture(documentKey, range, image);
            DocumentPicture picture = this.packageHost.GetSelectedPicture(documentKey, insertedRange, true);

            association.Picture = picture;
            association.ThumbnailImage = image.GetThumbnailImage();
        }

        /// <summary>
        /// Gets all pipeline associations in the model
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        /// <returns>
        /// ReadOnlyCollection of WorkflowAssociation
        /// </returns>
        public ReadOnlyCollection<WorkflowAssociation> GetAllWorkflowAssociations(int documentKey)
        {
            ReadOnlyCollection<DocumentPicture> documentPictures = this.packageHost.GetAllPictures(documentKey);
            Model currentModel = this.GetModelByDocumentKey(documentKey);
            List<int> toRemove = new List<int>();
            foreach (int key in currentModel.WorkflowAssociations.Keys)
            {
                WorkflowAssociation association = currentModel.WorkflowAssociations[key];
                // note that a null pa.Image means it's just being added -- don't delete it!
                if (association.Picture != null && !documentPictures.Contains(association.Picture))
                {
                    toRemove.Add(key);
                }
            }

            foreach (int deleteKey in toRemove)
            {
                currentModel.WorkflowAssociations.Remove(deleteKey);
            }

            return new ReadOnlyCollection<WorkflowAssociation>(new List<WorkflowAssociation>(currentModel.WorkflowAssociations.Values));
        }

        /// <summary>
        /// Gets all workflow associations.
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        /// <param name="documentPicture">The document picture.</param>
        /// <returns>ReadOnlyCollection of WorkflowAssociation</returns>
        public ReadOnlyCollection<WorkflowAssociation> GetAllWorkflowAssociations(int documentKey, DocumentPicture documentPicture)
        {
            if (documentPicture == null)
            {
                return new ReadOnlyCollection<WorkflowAssociation>(new List<WorkflowAssociation>());
            }

            Model currentModel = this.GetModelByDocumentKey(documentKey);
            List<WorkflowAssociation> workflowAssociations = new List<WorkflowAssociation>();
            foreach (WorkflowAssociation workflowAssociation in currentModel.WorkflowAssociations.Values)
            {
                if (workflowAssociation.Picture != null &&
                   workflowAssociation.Picture.Key.Compare(documentPicture.Key))
                {
                    workflowAssociations.Add(workflowAssociation);
                }
            }

            return new ReadOnlyCollection<WorkflowAssociation>(workflowAssociations);
        }

        /// <summary>
        /// Exports the package.
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        /// <param name="filePath">The file path.</param>
        /// <param name="activityId">The activity id.</param>
        /// <param name="server">The server.</param>
        public void ExportPackage(int documentKey, string filePath, Guid activityId, ITridentServer server)
        {
            Model model = this.GetModelByDocumentKey(documentKey);
            if (model.Modules.ContainsKey(activityId))
            {
                this.DataStore.CreateFileFromDocumentContent(documentKey, filePath, model.Modules[activityId].File.Guid);
                return;
            }

            server.ExportPackage(activityId, filePath);
        }

        /// <summary>
        /// Begins the transaction.
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        public void BeginModelTransaction(int documentKey)
        {
            Model currentModel = this.GetModelByDocumentKey(documentKey);
            Debug.Assert(!currentModel.TransactionActive, "Transaction already active on Active Model");
            currentModel.BeginTransaction();
        }

        /// <summary>
        /// Commits the model transaction.
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        public void CommitModelTransaction(int documentKey)
        {
            Model currentModel = this.GetModelByDocumentKey(documentKey);
            Debug.Assert(currentModel.TransactionActive, "No Transaction active on Active Model");
            currentModel.CommitTransaction();
        }

        /// <summary>
        /// Rolls the back model transaction.
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        public void RollbackModelTransaction(int documentKey)
        {
            Model currentModel = this.GetModelByDocumentKey(documentKey);
            Debug.Assert(currentModel != null, "Active model is null or is'nt in a transaction and can't be rolled back");
            Debug.Assert(currentModel.TransactionActive, "No Transaction active on Active Model");

            modelCollection[documentKey] = currentModel.RollbackTransaction();
        }

        /// <summary>
        /// Determines whether [is transaction active] [the specified document key].
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        /// <returns>
        /// 	<c>true</c> if [is transaction active] [the specified document key]; otherwise, <c>false</c>.
        /// </returns>
        public bool IsTransactionActive(int documentKey)
        {
            Model currentModel = this.GetModelByDocumentKey(documentKey);
            if (currentModel == null)
            {
                return false;
            }

            return currentModel.TransactionActive;
        }

        /// <summary>
        /// Checks the document protection.
        /// </summary>
        /// <returns>bool</returns>
        public bool CheckDocumentProtection(int documentKey)
        {
            if (this.packageHost.IsDocumentProtected(documentKey))
            {
                MessageBoxHelper.ShowDefault(ResourceFetcher.GetString("DocumentProtected"), MessageBoxIcon.Warning);
                return true;
            }

            return false;
        }

        /// <summary>
        /// Deletes the pipeline association.
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        /// <param name="associationId">The association id.</param>
        public void DeleteWorkflowAssociation(int documentKey, int associationId)
        {
            if (this.CheckDocumentProtection(documentKey))
            {
                return;
            }

            if (DialogResult.Yes == MessageBoxHelper.ShowYesNo(ResourceFetcher.GetString("DeleteWorkflowConfirmation")))
            {
                Model currentModel = this.GetModelByDocumentKey(documentKey);
                DocumentPicture picture = currentModel.WorkflowAssociations[associationId].Picture;
                currentModel.DeleteAssociationById(associationId);

                if (picture != null && picture.TridentLink)
                {
                    bool deleteImage = true;
                    foreach (WorkflowAssociation association in currentModel.WorkflowAssociations.Values)
                    {
                        if (association.Picture.Key == picture.Key)
                        {
                            deleteImage = false;
                        }
                    }

                    if (deleteImage)
                    {
                        this.packageHost.DeleteImage(documentKey, picture.Key);
                    }
                }

                this.AssociationChanged(documentKey, picture);
            }
        }

        /// <summary>
        /// Connections the changed.
        /// </summary>
        /// <param name="isCurrentConnection">if set to <c>true</c> [is current connection].</param>
        /// <param name="operation">The operation.</param>
        public void ConnectionChanged(bool isCurrentConnection, ConnectionOperation operation)
        {
            base.InvalidateControl(ControlKey.ConnectionDropDown);
            if (this.UserData.Connections.Count == 0)
            {
                RemovePane(TaskPaneKey.RerunWorkflow);
                RemovePane(TaskPaneKey.RunWorkflow);
                this.UpdateRibbon();
            }

            if (this.OnConnectionChanged != null)
            {
                ConnectionChangedEventArgs connEventArgs = new ConnectionChangedEventArgs(this.CurrentDocumentKey, this.CurrentConnection);
                connEventArgs.IsCurrentConnection = isCurrentConnection;
                connEventArgs.Operation = operation;

                this.OnConnectionChanged(this, connEventArgs);
            }
        }

        /// <summary>
        /// Associations the changed.
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        /// <param name="picture">The picture.</param>
        public void AssociationChanged(int documentKey, DocumentPicture picture)
        {
            this.packageHost.IsDocumentDirty = true;
            ReadOnlyCollection<WorkflowAssociation> associationList = this.GetAllWorkflowAssociations(documentKey);
            if (associationList.Count <= 1)
            {
                if (associationList.Count == 0)
                {
                    RemovePane(TaskPaneKey.Dashboard);
                    RemovePane(TaskPaneKey.ViewAssociations);
                    RemovePane(TaskPaneKey.RerunWorkflow);
                }

                this.UpdateRibbon();
            }

            if (this.OnAssociationChanged != null)
            {
                this.OnAssociationChanged(
                    this,
                    new AssociationChangedEventArgs(documentKey, picture, associationList));
            }
        }

        /// <summary>
        /// Reruns the clicked.
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        /// <param name="association">The association.</param>
        public void RerunClicked(int documentKey, WorkflowAssociation association)
        {
            if (this.OnRerunClicked != null)
            {
                this.OnRerunClicked(this, new SelectedAssociationEventArgs(documentKey, association));
            }
        }

        /// <summary>
        /// Reruns the completed.
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        /// <param name="association">The association.</param>
        public void RerunCompleted(int documentKey, WorkflowAssociation association)
        {
            if (this.OnRerunCompleted != null)
            {
                this.packageHost.IsDocumentDirty = true;
                this.OnRerunCompleted(this, new RerunCompletedEventArgs(documentKey, association));
            }
        }

        /// <summary>
        /// Workflows the by id.
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        /// <param name="workflowId">The workflow id.</param>
        /// <returns>Workflow</returns>
        public Workflow WorkflowById(int documentKey, Guid workflowId)
        {
            Workflow workflow = null;
            Model model = this.GetModelByDocumentKey(documentKey);
            if (model.Workflows.ContainsKey(workflowId))
            {
                workflow = model.Workflows[workflowId];
            }

            return workflow;
        }

        /// <summary>
        /// Tests the connection and prompt if required.
        /// </summary>
        /// <param name="connection">The connection.</param>
        /// <param name="connectionReturn">The connection which is returned after user provided credential</param>
        /// <param name="deleteOnCancel">if true the ConnectionForm will ask confirmation when user choose cancel or close for removing the connection</param>
        /// <returns>bool</returns>
        public bool TestConnectionAndPromptIfRequired(TridentConnection connection, ref TridentConnection connectionReturn, bool deleteOnCancel)
        {
            ConnectionFailedException connectionFailedException = new ConnectionFailedException();
            bool isConnectionSuccess = TridentServer.TestConnection(connection, ref connectionFailedException);
            if (isConnectionSuccess)
            {
                connectionReturn = connection;
                return true;
            }

            bool success = this.ShowServerConnectionForm(ref connection, true, deleteOnCancel);
            if (success)
            {
                connectionReturn = connection;
            }

            return success;
        }

        /// <summary>
        /// Sets the next default connection.
        /// </summary>
        public void SetNextDefaultConnection()
        {
            IDictionaryEnumerator iterator = this.UserData.Connections.GetEnumerator();
            if (!iterator.MoveNext())
            {
                this.CurrentConnection = null;
                this.ConnectionChanged(true, ConnectionOperation.Changed);
                return;
            }

            TridentConnection connectionReturn = new TridentConnection();
            if (this.TestConnectionAndPromptIfRequired(iterator.Value as TridentConnection, ref connectionReturn, true))
            {
                this.CurrentConnection = connectionReturn;
                this.ConnectionChanged(true, ConnectionOperation.Changed);
                return;
            }
            else
            {
                this.RemoveConnection((iterator.Value as TridentConnection).Name);
                this.ConnectionChanged(false, ConnectionOperation.Deleted);
                this.SetNextDefaultConnection();
            }
        }

        /// <summary>
        /// Adds the appendix entry.
        /// </summary>
        /// <param name="insertIntoDocumentData">The insert into document data.</param>
        public void AddAppendixEntry(AppendixResult insertIntoDocumentData)
        {
            this.packageHost.AddAppendixEntry(insertIntoDocumentData);
        }

        /// <summary>
        /// Determines whether [is connection present].
        /// </summary>
        /// <returns>
        /// 	<c>true</c> if [is connection present]; otherwise, <c>false</c>.
        /// </returns>
        public bool IsConnectionPresent()
        {
            return (this.UserData.Connections.Count > 0);
        }

        /// <summary>
        /// Determines whether [is association present].
        /// </summary>
        /// <returns>
        /// 	<c>true</c> if [is association present]; otherwise, <c>false</c>.
        /// </returns>
        public bool IsAssociationsPresent()
        {
            return (modelCollection[this.CurrentDocumentKey].WorkflowAssociations.Count > 0);
        }     

        /// <summary>
        /// Determines whether [is connection and assoication present].
        /// </summary>
        /// <returns>
        /// 	<c>true</c> if [is connection and assoication present]; otherwise, <c>false</c>.
        /// </returns>
        public bool IsConnectionAndAssociationsPresent()
        {
            return ((this.UserData.Connections.Count > 0) &&
                    (modelCollection[this.CurrentDocumentKey].WorkflowAssociations.Count > 0));
        }
        
        /// <summary>
        /// Selects the picture by document.
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        /// <param name="pictureKey">The picture key.</param>
        public void SelectPictureByDocument(int documentKey, string pictureKey)
        {
            this.packageHost.SelectPicture(documentKey, pictureKey);
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Handles the OnAddInShutdown event of the packageHost control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void packageHost_OnAddInShutdown(object sender, EventArgs e)
        {
            if (tridentServer != null)
            {
                tridentServer.Dispose();
            }
        }

        /// <summary>
        /// Handles the DocumentSaved event of the host control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="Microsoft.TridentWordAddIn.Common.DocumentSaveEventArgs"/> instance containing the event data.</param>
        private void packageHost_DocumentSaving(object sender, DocumentSaveEventArgs e)
        {
            int documentKey = this.CurrentDocumentKey;
            if (!this.packageHost.IsDocumentProtected(documentKey))
            {
                Model model = this.GetModelByDocumentKey(documentKey);
                this.CleanDataStore(documentKey);
                this.DataStore.SerializeModel(documentKey, model);
            }
        }

        /// <summary>
        /// Handles the DocumentClose event of the host control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void packageHost_DocumentClosing(object sender, EventArgs e)
        {
            bool isDirty = this.packageHost.IsDocumentDirty;
            Model model = this.GetModelByDocumentKey(this.CurrentDocumentKey);
            if (model.IsRerunResultInserted())
            {
                CancelCloseEventArgs cancelEventArgs = e as CancelCloseEventArgs;
                if (cancelEventArgs != null)
                {
                    if (MessageBoxHelper.ShowYesNo(ResourceFetcher.GetString("DiscardRerunResult")) == DialogResult.No)
                    {
                        cancelEventArgs.Cancel = true;
                        return;
                    }
                }
            }

            if (this.packageHost.DocumentsCount == 1)
            {
                this.SaveUserData();
            }

            if (this.OnDocumentClosing != null)
            {
                this.OnDocumentClosing(this, new BaseEventArgs(this.CurrentDocumentKey));
            }

            UpdateRibbon();
            base.RemoveTaskPanes();
            this.packageHost.IsDocumentDirty = isDirty;
        }

        /// <summary>
        /// Handles the DocumentOpen event of the host control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void packageHost_DocumentOpen(object sender, EventArgs e)
        {
            IDictionaryEnumerator enumerator = this.modelCollection.GetEnumerator();
            while (enumerator.MoveNext())
            {
                int documentKey = (int)enumerator.Key;
                if (!this.packageHost.ModelExists(documentKey))
                {
                    this.modelCollection.Remove(documentKey);
                }
            }

            Model model = this.documentStore.DeserializeModel(this.CurrentDocumentKey);
            model = model ?? new Model();
            this.modelCollection.Add(CurrentDocumentKey, model);
        }

        /// <summary>
        /// Handles the DocumentActivated event of the host control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void packageHost_DocumentActivated(object sender, EventArgs e)
        {
            UpdateRibbon();
        }

        /// <summary>
        /// Handles the OnPictureSelectionChanged event of the packageHost control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="Microsoft.TridentWordAddIn.Common.PictureSelectionChangedEventArgs"/> instance containing the event data.</param>
        private void packageHost_OnPictureSelectionChanged(object sender, PictureSelectionChangedEventArgs e)
        {
            if (this.OnPictureSelectionChanged != null)
            {
                this.OnPictureSelectionChanged(this, e);
            }
        }

        #endregion

        #region Private Members

        /// <summary>
        /// Deletes the garbage workflows.
        /// </summary>
        /// <param name="idList">The id list.</param>
        /// <param name="model">The model.</param>
        private static void DeleteGarbageWorkflows(List<string> idList, Model model)
        {
            //Delete dead Workflows 
            List<string> deleteWorkflowIds = new List<string>();
            foreach (Workflow workflow in model.Workflows.Values)
            {
                if (!idList.Contains(workflow.StringId))
                {
                    deleteWorkflowIds.Add(workflow.StringId);
                }
            }

            foreach (string id in deleteWorkflowIds)
            {
                model.Workflows.Remove(new Guid(id));
            }

            //Remove linked workflows ids from list, so that OPC package will be deleted
            foreach (Workflow workflow in model.Workflows.Values)
            {
                if (workflow.Linked && idList.Contains(workflow.StringId))
                {
                    idList.Remove(workflow.StringId);
                    model.Modules.Remove(workflow.Id);
                }
            }
        }

        /// <summary>
        /// Gets the trident server.
        /// </summary>
        /// <returns>ITridentServer</returns>
        public ITridentServer GetTridentServer()
        {
            ITridentServer server = new TridentServer();
            server.CurrentTridentConnection = this.CurrentConnection;
            return server;
        }

        /// <summary>
        /// Saves the user data.
        /// </summary>
        private void SaveUserData()
        {
            Trace.WriteLine("Saving user data", TraceLevel.Info.ToString());
            if (this.userData != null)
            {
                UserDataManager.Save(this.userData);
                Trace.WriteLine("User data saved", TraceLevel.Info.ToString());
            }
        }

        /// <summary>
        /// Progresses the specified document key.
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        /// <param name="message">The message.</param>
        private void Progress(int documentKey, string message)
        {
            if (this.AssociationProgress != null)
            {
                this.AssociationProgress(this, new ProgressEventArgs(documentKey, message));
            }
        }

        /// <summary>
        /// Stores the workflow.
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        /// <param name="workflowId">The workflow id.</param>
        /// <param name="server">The server.</param>
        private void StoreWorkflow(int documentKey, Guid workflowId, ITridentServer server)
        {
            this.Progress(documentKey, "Downloading workflow package...");
            using (MemoryStream packageZip = server.DownloadPackage(workflowId))
            {
                this.Progress(documentKey, "Downloading workflow package completed!");
                Module module = new Module();
                module.Id = workflowId;
                module.File = new DocumentFile();
                module.File.FileName = workflowId + ".zip";

                this.Progress(documentKey, "Storing workflow package...");
                DocumentFile storedFile = this.DataStore.StoreData(documentKey, workflowId.ToString(), packageZip);
                module.File.Guid = storedFile.Guid;
                module.File.Length = storedFile.Length;

                this.GetModelByDocumentKey(documentKey).AddModule(module);
                this.Progress(documentKey, "Workflow package stored!");
            }
        }

        /// <summary>
        /// Cleans the data store.
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        private void CleanDataStore(int documentKey)
        {
            List<string> idList = new List<string>();
            Model model = this.GetModelByDocumentKey(documentKey);

            this.DeleteGarbageAssociation(documentKey, idList, model);
            DeleteGarbageWorkflows(idList, model);

            if (model.WorkflowAssociations.Count == 0)
            {
                this.DataStore.Clear(documentKey);
            }
            else
            {
                this.DataStore.Compress(documentKey, idList);
            }
        }

        /// <summary>
        /// Deletes the garbage association.
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        /// <param name="idList">The id list.</param>
        /// <param name="model">The model.</param>
        private void DeleteGarbageAssociation(int documentKey, List<string> idList, Model model)
        {
            List<int> deleteAssociationIds = new List<int>();
            ReadOnlyCollection<DocumentPicture> documentPictures = this.packageHost.GetAllPictures(documentKey);
            foreach (WorkflowAssociation association in model.WorkflowAssociations.Values)
            {
                if (documentPictures.Contains(association.Picture))
                {
                    string workflowId = association.WorkflowId.ToString();
                    if (!idList.Contains(workflowId))
                    {
                        idList.Add(workflowId);
                    }

                    foreach (ShallowJobOutput output in association.Job.Outputs)
                    {
                        if (!idList.Contains(output.Id.ToString()))
                        {
                            idList.Add(output.Id.ToString());
                        }
                    }
                }
                else
                {
                    deleteAssociationIds.Add(association.LocalId);
                }
            }

            foreach (int id in deleteAssociationIds)
            {
                model.WorkflowAssociations.Remove(id);
            }
        }

        /// <summary>
        /// Updates the active connection list.
        /// </summary>
        /// <param name="connectionName">Name of the connection.</param>
        private void UpdateActiveConnectionList(string connectionName)
        {
            IDictionaryEnumerator iter = this.ribbonControlsState.GetEnumerator();
            while (iter.MoveNext())
            {
                if ((iter.Value as RibbonControlStateManager).Connection.Compare(connectionName))
                {
                    (iter.Value as RibbonControlStateManager).Connection = string.Empty;
                }
            }
        }

        /// <summary>
        /// Downloads the and store job output.
        /// </summary>
        /// <param name="documentKey">The document key.</param>
        /// <param name="jobId">The job id.</param>
        /// <param name="server">The server.</param>
        private void DownloadAndStoreJobOutput(int documentKey, Guid jobId, ITridentServer server)
        {
            this.Progress(documentKey, "Downloading job results...");
            JobOutputCollection jobOutputs = server.GetJobOutputs(jobId);
            if (jobOutputs != null)
            {
                this.Progress(documentKey, "Storing job results...");
                foreach (JobOutput output in jobOutputs)
                {
                    if (output.Contents != null)
                    {
                        DocumentFile documentFile = this.DataStore.StoreData(documentKey, output.Id.ToString(), new MemoryStream(output.Contents));
                        Module module = new Module();
                        module.Id = output.Id;
                        module.File = new DocumentFile();
                        module.File.FileName = output.FileName;
                        module.File.Guid = documentFile.Guid;
                        module.File.Length = documentFile.Length;
                        this.GetModelByDocumentKey(documentKey).AddModule(module);
                    }
                }
            }
        }
       
        #endregion
    }
}
